package com.cm.dynamic.scan;

import com.cm.dynamic.autoconfigure.Configuration;
import com.cm.dynamic.classloader.context.ClassLoaderContext;
import com.cm.dynamic.classloader.factory.ClassLoaderFactory;
import com.cm.dynamic.util.SpringContextUtil;
import org.apache.ibatis.builder.xml.XMLMapperBuilder;
import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.mapper.ClassPathMapperScanner;
import org.mybatis.spring.mapper.MapperFactoryBean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import org.springframework.context.annotation.ScannedGenericBeanDefinition;
import org.springframework.core.MethodIntrospector;
import org.springframework.core.NestedIOException;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.mvc.condition.RequestCondition;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import java.io.IOException;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.util.*;

/**
 * 模块自定义扫描spring bean注入到容器中
 * @author xionglei
 * @version 1.0.0
 * 2021年08月23日 10:34
 */
public class ModuleClassPathScanner extends ClassPathBeanDefinitionScanner {

    private static ModuleClassPathScanner moduleClassPathScanner = null;

    private static ModuleClassPathMapperScanner moduleClassPathMapperScanner = null;

    private static final Logger LOGGER = LoggerFactory.getLogger(ModuleClassPathScanner.class);

    private ModuleClassPathScanner(BeanDefinitionRegistry registry, ClassLoader classLoader) {
        super(registry);
        // 设置 resourceLoader
        if (classLoader != null) {
            PathMatchingResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver(classLoader);
            super.setResourceLoader(resourcePatternResolver);
        }
    }

    /**
     * 获取单例的ClassPathScan扫描器实例
     * @return
     */
    public static synchronized ModuleClassPathScanner getInstance() {
        // 获取当前上下文的类加载器
        ClassLoader classLoader = ClassLoaderContext.currentClassLoader();
        if (moduleClassPathScanner == null) {
            DefaultListableBeanFactory beanFactory = SpringContextUtil.getBeanFactory();
            moduleClassPathScanner = new ModuleClassPathScanner(beanFactory, classLoader);
        }
        if (moduleClassPathMapperScanner == null) {
            DefaultListableBeanFactory beanFactory = SpringContextUtil.getBeanFactory();
            if (moduleClassPathScanner != null) {
                ResourceLoader resourceLoader = moduleClassPathScanner.getResourceLoader();
                moduleClassPathMapperScanner = new ModuleClassPathMapperScanner(beanFactory, resourceLoader);
            }
            moduleClassPathMapperScanner.registerFilters();
        }
        return moduleClassPathScanner;
    }

    public static ModuleClassPathMapperScanner getModuleClassPathMapperScanner() {
        return moduleClassPathMapperScanner;
    }

    /**
     * Calls the parent search that will search and register all the candidates. Then the registered objects are post
     * processed to set them as MapperFactoryBeans
     */
    public Set<BeanDefinitionHolder> doScan(Configuration configuration) {
        Set<BeanDefinitionHolder> definitionHolders = new LinkedHashSet<>();
        // 1.扫描动态加载jar的spring bean
        String[] basePackages = configuration.getBasePackages();
        if (basePackages != null && basePackages.length > 0) {
            Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
            if (beanDefinitions.isEmpty()) {
                LOGGER.warn("No spring bean was found in '" + Arrays.toString(basePackages)
                        + "' package. Please check your configuration.");
            } else {
                // 扩展,扫描Controller RequestMapping 进行AbstractHandlerMethodMapping 的 方法initHandlerMethods初始化
                processBeanDefinitions(beanDefinitions);
                definitionHolders.addAll(beanDefinitions);
            }
        }
        String[] mapperScanPackages = configuration.getMapperScanPackages();
        if (mapperScanPackages != null && mapperScanPackages.length > 0) {
            // 2.扫描动态加载jar下的mybatis mapper 类
            Set<BeanDefinitionHolder> beanDefinitionHolders = moduleClassPathMapperScanner.doScan(mapperScanPackages);
            if (beanDefinitionHolders.isEmpty()) {
                LOGGER.warn("No mybatis mapper was found in '" + Arrays.toString(mapperScanPackages)
                        + "' package. Please check your configuration.");
            } else {
                // 3.扩展,初始化绑定MapperFactoryBean 和 MapperRegistry 扫描mapper.xml
                processMapper(beanDefinitionHolders, configuration);
                definitionHolders.addAll(beanDefinitionHolders);
            }
        }
        return definitionHolders;
    }

    /**
     * 初始化绑定MapperFactoryBean 和 MapperRegistry 扫描mapper.xml
     * @param beanDefinitionHolders
     * @param configuration
     */
    private void processMapper(Set<BeanDefinitionHolder> beanDefinitionHolders, Configuration configuration) {
        ClassLoader contextClassLoader = ClassLoaderContext.currentClassLoader();
        /*DefaultListableBeanFactory beanFactory = SpringContextUtil.getBeanFactory();
        for (BeanDefinitionHolder beanDefinitionHolder: beanDefinitionHolders) {
            if (beanFactory.containsBeanDefinition(beanDefinitionHolder.getBeanName())) {
                GenericBeanDefinition definition = (GenericBeanDefinition) beanFactory.getBeanDefinition(beanDefinitionHolder.getBeanName());
                try {
                    String className = ((ScannedGenericBeanDefinition) definition).getMetadata().getClassName();
                    Class<?> mapperClass = contextClassLoader.loadClass(className);
                    definition.setBeanClass(new MapperFactoryBean(mapperClass).getClass());
                    definition.getConstructorArgumentValues().clear();
                    definition.getConstructorArgumentValues().addGenericArgumentValue(mapperClass);
                } catch (ClassNotFoundException e) {
                    LOGGER.error("找不到类：" + e, e);
                    throw new RuntimeException(e);
                }
            }
        }*/
        // 添加mybatis mapper配置文件
        SqlSessionFactory sqlSessionFactory = SpringContextUtil.getApplicationContext().getBean(SqlSessionFactory.class);
        org.apache.ibatis.session.Configuration mybatisConfiguration = sqlSessionFactory.getConfiguration();
        PathMatchingResourcePatternResolver resourcePatternResolver  = new PathMatchingResourcePatternResolver(contextClassLoader);
        try {
            String[] mappingLocations = configuration.getMapperLocations();
            if (mappingLocations != null && mappingLocations.length != 0) {
                for (String mappingLocation : mappingLocations) {
                    Resource[] mapperLocations = resourcePatternResolver.getResources(mappingLocation);
                    // 将这些resources加载进configuration中
                    for (Resource mapperLocation : mapperLocations) {
                        if (mapperLocation == null) {
                            continue;
                        }
                        ClassLoader threadClassLoader = Thread.currentThread().getContextClassLoader();
                        try {
                            Thread.currentThread().setContextClassLoader(contextClassLoader);
                            XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
                                    mybatisConfiguration, mapperLocation.toString(), mybatisConfiguration.getSqlFragments());
                            xmlMapperBuilder.parse();
                        } catch (Exception e) {
                            throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
                        } finally {
                            Thread.currentThread().setContextClassLoader(threadClassLoader);
                            ErrorContext.instance().reset();
                        }
                        logger.debug("Parsed mapper file: '" + mapperLocation + "'");
                    }
                }
            }

        } catch (IOException e) {
            logger.error("加载当前线程类加载器mybatis mapper.xml异常" + e, e);
            throw new RuntimeException("加载当前线程类加载器mybatis mapper.xml异常");
        }
    }

    /**
     * 动态注入Spring bean后扩展
     * @param beanDefinitions
     */
    private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
        ClassLoader contextClassLoader = ClassLoaderContext.currentClassLoader();
        DefaultListableBeanFactory beanFactory = SpringContextUtil.getBeanFactory();
        for (BeanDefinitionHolder beanDefinitionHolder: beanDefinitions) {
            if (beanFactory.containsBeanDefinition(beanDefinitionHolder.getBeanName())) {
                try {
                    GenericBeanDefinition definition = (GenericBeanDefinition) beanFactory.getBeanDefinition(beanDefinitionHolder.getBeanName());
                    // 默认是bean的全类名
                    String className = ((ScannedGenericBeanDefinition) definition).getMetadata().getClassName();
                    definition.setBeanClass(contextClassLoader.loadClass(className));
                } catch (ClassNotFoundException e) {
                    LOGGER.error("找不到类：" + e, e);
                    throw new RuntimeException(e);
                }
            }
        }
        // 再次刷新SpringMvc handlerMapper 和handlerMapperAdapter
        RequestMappingHandlerMapping requestMappingHandlerMapping = beanFactory.getBean(RequestMappingHandlerMapping.class);
        // 先判断是否已经存在相同的@RequestMapping 方法
        beanDefinitions.forEach(beanDefinitionHolder -> {
            String beanName = beanDefinitionHolder.getBeanName();
            ScannedGenericBeanDefinition genericBeanDefinition = (ScannedGenericBeanDefinition)beanDefinitionHolder.getBeanDefinition();
            Class<?> beanClass = genericBeanDefinition.getBeanClass();
            Class<?> userType = ClassUtils.getUserClass(beanClass);
            Map<Method, RequestMappingInfo> methods = MethodIntrospector.selectMethods(userType,
                    (MethodIntrospector.MetadataLookup<RequestMappingInfo>) method -> {
                        try {
                            return getMappingForMethod(method, userType);
                        }
                        catch (Throwable ex) {
                            throw new IllegalStateException("Invalid mapping on handler class [" +
                                    userType.getName() + "]: " + method, ex);
                        }
                    });
            methods.forEach((method, mapping) -> {
                Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
                requestMappingHandlerMapping.registerMapping(mapping, beanName,invocableMethod);
                requestMappingHandlerMapping.getPathPrefixes();
            });

        });
    }

    /**
     * 构造RequestMappingInfo
     * @param method
     * @param handlerType
     * @return
     */
    private RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
        RequestMappingInfo info = createRequestMappingInfo(method);
        if (info != null) {
            RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
            if (typeInfo != null) {
                info = typeInfo.combine(info);
            }
        }
        return info;
    }

    private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {
        RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);
        return (requestMapping != null ? createRequestMappingInfo(requestMapping, null) : null);
    }

    protected RequestMappingInfo createRequestMappingInfo(
            RequestMapping requestMapping, @Nullable RequestCondition<?> customCondition) {

        RequestMappingInfo.Builder builder = RequestMappingInfo
                .paths(requestMapping.path())
                .methods(requestMapping.method())
                .params(requestMapping.params())
                .headers(requestMapping.headers())
                .consumes(requestMapping.consumes())
                .produces(requestMapping.produces())
                .mappingName(requestMapping.name());
        if (customCondition != null) {
            builder.customCondition(customCondition);
        }
        return builder.options(builderConfiguration()).build();
    }

    private RequestMappingInfo.BuilderConfiguration builderConfiguration() {
        RequestMappingHandlerMapping requestMappingHandlerMapping = SpringContextUtil.getApplicationContext().getBean(RequestMappingHandlerMapping.class);
        RequestMappingInfo.BuilderConfiguration config = new RequestMappingInfo.BuilderConfiguration();
        config.setUrlPathHelper(requestMappingHandlerMapping.getUrlPathHelper());
        config.setPathMatcher(requestMappingHandlerMapping.getPathMatcher());
        config.setSuffixPatternMatch(requestMappingHandlerMapping.useSuffixPatternMatch());
        config.setTrailingSlashMatch(requestMappingHandlerMapping.useTrailingSlashMatch());
        config.setRegisteredSuffixPatternMatch(requestMappingHandlerMapping.useRegisteredSuffixPatternMatch());
        config.setContentNegotiationManager(requestMappingHandlerMapping.getContentNegotiationManager());
        return config;
    }


}
